/*
 * anima/x86_64/pc/firmware.cc - Firmware functions for x86-64+PC machine.
 * Copyright (C) 2007  Mikhail Vorozhtsov
 * See the LICENSE section of the README file for details.
 */

/* $Id$ */

#include HEADER (anima,firmware.hh)
#include HEADER (anima/common,acpi.hh)
#include HEADER (anima,console.hh)
#include HEADER (anima,mm.hh)
#include HEADER (anima,cpu.hh)
#include HEADER (x86_64,msrs.hh)
#include HEADER (x86_64,io.hh)
#include HEADER (x86_64,ioapic.hh)
#include HEADER (utils,memory.hh)

START_NAMESPACE2 (anima,x86_64)

using ::anima::common::acpi;

u64_t firmware::lapic_base;
u64_t firmware::timer_freq;
u64_t firmware::timer_period;

static const ::acpi::madt_t *madt = (const ::acpi::madt_t *) U64 (-1);
static const ::acpi::fadt_t *fadt = (const ::acpi::fadt_t *) U64 (-1);

static u32_t madt_len;
static const ::acpi::madt_t::ioapic_t *first_ioapic;
static u16_t pm_timer_port;

bool
firmware::init (const elbp::info_t *elbp_info UNUSED)
{
  using ::x86_64::out8;

  // Initialize master PIC.
  out8 (0x20, 0x11);
  out8 (0x21, cpu_t::INT_APIC_TIMER);
  out8 (0x21, 0x04);
  out8 (0x21, 0x01);
  out8 (0x21, 0xFF);

  // Initialize slave PIC.
  out8 (0xA0, 0x11);
  out8 (0xA1, cpu_t::INT_APIC_TIMER + 8);
  out8 (0xA1, 0x02);
  out8 (0xA1, 0x01);
  out8 (0xA1, 0xFF);

  // Switch to Symmetric I/O Mode.
  out8 (0x22, 0x70);
  out8 (0x23, 0x01);

  if (acpi::init (0xE0000, MB (1)))
    {
      if (!acpi::find (fadt))
        console::warning ("Cannot find ACPI FADT.");
      if (!acpi::find (madt))
        console::warning ("Cannot find ACPI MADT.");
    }
  else
    console::warning ("Cannot find ACPI tables.");

  if ((u64_t) fadt == U64 (-1))
    {
      // Initialize PIT.
      out8 (0x43, 0x30);
      out8 (0x40, 0xFF);
      out8 (0x40, 0xFF);

      timer_freq = 1193182;
      timer_period = 0x10000;
    }
  else
    {
      if (fadt->len > offsetof (::acpi::fadt_t, x_pm_tmr_blk))
        pm_timer_port = fadt->x_pm_tmr_blk.addr & 0xFFFF;
      else
        pm_timer_port = fadt->pm_tmr_blk & 0xFFFF;

      timer_freq = 3579545;
      timer_period = fadt->flags.TMR_VAL_EXT ? U64 (0x100000000) : 0x1000000;
    }

  if ((u64_t) madt == U64 (-1))
    {
      ::x86_64::apic_base_t apic_base = ::x86_64::apic_base_t::get ();

      if (apic_base.E)
        lapic_base = apic_base.get_ptr ();
      else
        lapic_base = 0xFEE00000;

      console::warning ("Using %p as Local APIC base address.", lapic_base);
      console::warning ("Using %p as I/O APIC base address.", 0xFEC00000);
      return true;
    }

  madt_len = madt->len;
  lapic_base = madt->lapic_base;

  for (const ::acpi::madt_t::entry_t *entry
         = (const ::acpi::madt_t::entry_t *)
             ((const void *) madt + sizeof (::acpi::madt_t));
       (u64_t) entry <= (u64_t) madt + madt->len
                        - sizeof (::acpi::madt_t::entry_t)
       && (u64_t) entry + entry->len <= (u64_t) madt + madt->len;
       entry = (const ::acpi::madt_t::entry_t *)
                 ((const void *) entry + entry->len))
    {
      if (entry->type == ::acpi::madt_t::entry_t::TYPE_LAPIC_OVERRIDE)
        lapic_base = ((const ::acpi::madt_t::lapic_override_t *) entry)->addr;
      if (entry->type == ::acpi::madt_t::entry_t::TYPE_IOAPIC
          && first_ioapic == NULL)
        first_ioapic = (const ::acpi::madt_t::ioapic_t *) entry;
    }

  if (first_ioapic == NULL)
    {
      console::warning ("ACPI MADT does not contain I/O APIC descriptions.");
      console::warning ("Using %p as I/O APIC base address.", 0xFEC00000);
    }

  return true;
}

u64_t
firmware::read_timer ()
{
  if (pm_timer_port != 0)
    return ::x86_64::in32 (pm_timer_port);

  ::x86_64::out8 (0x43, 0x00);
  u8_t lsb = ::x86_64::in8 (0x40);
  u8_t msb = ::x86_64::in8 (0x40);
  return (u64_t) 0x10000 - (((u64_t) msb << 8) | lsb);
}

const void *
firmware::ioapic_t::next (const void *prev, ioapic_t &ioapic)
{
  if (first_ioapic == NULL)
    {
      if (prev)
        return NULL;

      ioapic.addr = 0xFEC00000;
      ioapic.ints_base = 0;

      return (const void *) U64 (1);
    }

  const ::acpi::madt_t::ioapic_t *ioapic_entry;
  ::acpi::madt_t::ioapic_t ioapic_entry_copy;
  u64_t ioapic_entry_paddr = 0;

  if (prev)
    {
      for (u64_t entry_paddr = (u64_t) prev;
           entry_paddr <= (u64_t) madt + madt_len
                          - sizeof (::acpi::madt_t::entry_t);)
        {
          const ::acpi::madt_t::entry_t *entry;
          ::acpi::madt_t::entry_t entry_copy;

          if (::anima::mm::initialized_p ())
            {
              if (!::anima::mm::cp (entry_paddr, &entry_copy,
                                    sizeof (entry_copy)))
                return NULL;
              entry = &entry_copy;
            }
          else
            entry = (const ::acpi::madt_t::entry_t *) entry_paddr;

          if (entry_paddr + entry->len > (u64_t) madt + madt_len)
            return NULL;

          if (entry->type == ::acpi::madt_t::entry_t::TYPE_IOAPIC
              && entry_paddr != (u64_t) prev)
            {
              ioapic_entry_paddr = entry_paddr;
              break;
            }

          entry_paddr += entry->len;
        }
    }
  else
    ioapic_entry_paddr = (u64_t) first_ioapic;

  if (ioapic_entry_paddr == 0)
    return NULL;

  if (::anima::mm::initialized_p ())
    {
      if (!::anima::mm::cp ((u64_t) ioapic_entry_paddr, &ioapic_entry_copy,
                            sizeof (ioapic_entry_copy)))
        return NULL;
      ioapic_entry = &ioapic_entry_copy;
    }
  else
    ioapic_entry = (const ::acpi::madt_t::ioapic_t *) ioapic_entry_paddr;

  ioapic.addr = ioapic_entry->addr;
  ioapic.ints_base = ioapic_entry->ints_base;

  return (const void *) ioapic_entry_paddr;
}

END_NAMESPACE2

